package pipeline;

import baseObjects.Language;
import baseObjects.Symbol;
import baseObjects.XML_Handler;
import corpus.Observable;
import corpus.Observer;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Properties;
import java.text.*;
import java.util.Vector;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

public class KeyboardProducer extends Thread implements Observable {

    public static final String FINISH = "FINISH";
    public static final String PRODUCED = "PRODUCED";
//    public static final String TOTAL = "TOTAL";
    private boolean run = true;
    private boolean makeRestorepoint = true;
    protected SynchronizedBuffer keyboards = null;
    protected Language lang = null;
    protected Properties keyPriority = null;
    protected BigInteger numOfPermutations;
    protected Vector<Observer> observers = null;
    protected boolean generic;
    //saved data
    protected int Priority1Amount = 0;
    protected int Priority2Amount = 0;
    protected int Priority3Amount = 0;
    protected int Priority4Amount = 0;
    protected int Priority5Amount = 0;
    protected int Priority6Amount = 0;
    static protected int NumOfKeysInLayout = 47;
    private int updateCounter = 100;

    public int getUpdateCounter() {
        return updateCounter;
    }

    public void setUpdateCounter(int updateCounter) {
        this.updateCounter = updateCounter;
    }
    /**keycode->priority**/
    protected HashMap<Integer, Integer> priorityMap = null;
    /**symbol->priority**/
    protected HashMap<Symbol, Integer> symbolPrioroty = null;
    protected Logger success = Logger.getLogger(this.getClass().getName() + ".success");
    protected Logger failed = Logger.getLogger(this.getClass().getName() + ".failed");

    public KeyboardProducer(SynchronizedBuffer buff, Language lang, Properties keyPriority) {
        //System.out.println("KeyboardProducer ctor");
        FileHandler fh, fh2;
        try {
            fh = new FileHandler(this.getClass().getName() + ".success", 100 * 1024 * 1024, 5, true);
            fh2 = new FileHandler(this.getClass().getName() + ".failed", 100 * 1024 * 1024, 5, true);
            success.addHandler(fh);
            failed.addHandler(fh2);
            success.setLevel(Level.ALL);
            failed.setLevel(Level.ALL);
            fh.setFormatter(new SimpleFormatter());
            fh2.setFormatter(new SimpleFormatter());

        } catch (Throwable t) {
            System.err.println(t.getMessage());
        }
        keyboards = buff;
        this.lang = lang;
        this.keyPriority = keyPriority;
        this.numOfPermutations = calcPermutations();
        observers = new Vector<Observer>();
        //System.out.printf("perms: %,d\n", numOfPermutations);
        generic = false;

        priorityMap = new HashMap<Integer, Integer>();

        symbolPrioroty = new HashMap<Symbol, Integer>();

        Vector<Symbol> pri = lang.getSymbolsSortet();

        Priority1Amount = keyPriority.getProperty("1").split(",").length;
        //System.out.println("priority1 amount = " + Priority1Amount);

        Priority2Amount = keyPriority.getProperty("2").split(",").length;
        //System.out.println("priority2 amount = " + Priority2Amount);

        Priority3Amount = keyPriority.getProperty("3").split(",").length;
        //System.out.println("priority3 amount = " + Priority3Amount);

        Priority4Amount = keyPriority.getProperty("4").split(",").length;
        //System.out.println("priority4 amount = " + Priority4Amount);

        Priority5Amount = keyPriority.getProperty("5").split(",").length;
        //System.out.println("priority5 amount = " + Priority5Amount);

        Priority6Amount = keyPriority.getProperty("6").split(",").length;
        //System.out.println("priority6 amount = " + Priority6Amount);

        int place = 0;
        while (place < pri.size()) {
            Object[] arr = keyPriority.getProperty("1").split(",");
            Integer p = new Integer(1);
            for (int i = 0; i < Priority1Amount && place < pri.size(); i++) {
                if (!Character.isDigit(pri.get(place).getRegular())) {
                    priorityMap.put(Integer.parseInt(((String) arr[i]).trim()), p);
                    symbolPrioroty.put(pri.get(place), p);
//                    System.out.println("symbol "+pri.get(place)+" priority is "+p);
//                    System.out.println("verify: "+symbolPrioroty.get(pri.get(place)));
                } else {
                    i--;
                }
                place++;

            }
//            new Scanner(System.in).next();
            arr = keyPriority.getProperty("2").split(",");
            p = new Integer(2);
            for (int i = 0; i < Priority2Amount && place < pri.size(); i++) {
                if (!Character.isDigit(pri.get(place).getRegular())) {
                    priorityMap.put(Integer.parseInt(((String) arr[i]).trim()), p);
                    symbolPrioroty.put(pri.get(place), p);
                } else {
                    i--;
                }
                place++;
            }

            arr = keyPriority.getProperty("3").split(",");
            p = new Integer(3);
            for (int i = 0; i < Priority3Amount && place < pri.size(); i++) {
                if (!Character.isDigit(pri.get(place).getRegular())) {
                    priorityMap.put(Integer.parseInt(((String) arr[i]).trim()), p);
                    symbolPrioroty.put(pri.get(place), p);
                } else {
                    i--;
                }
                place++;
            }

            arr = keyPriority.getProperty("4").split(",");
            p = new Integer(4);
            for (int i = 0; i < Priority4Amount && place < pri.size(); i++) {
                if (!Character.isDigit(pri.get(place).getRegular())) {
                    priorityMap.put(Integer.parseInt(((String) arr[i]).trim()), p);
                    symbolPrioroty.put(pri.get(place), p);
                } else {
                    i--;
                }
                place++;
            }

            arr = keyPriority.getProperty("5").split(",");
            p = new Integer(5);
            for (int i = 0; i < Priority5Amount && place < pri.size(); i++) {
                if (!Character.isDigit(pri.get(place).getRegular())) {
                    priorityMap.put(Integer.parseInt(((String) arr[i]).trim()), p);
                    symbolPrioroty.put(pri.get(place), p);
                } else {
                    i--;
                }
                place++;
            }

            arr = keyPriority.getProperty("6").split(",");
            p = new Integer(6);
            for (int i = 0; i < Priority6Amount && place < pri.size(); i++) {
                if (!Character.isDigit(pri.get(place).getRegular())) {
                    priorityMap.put(Integer.parseInt(((String) arr[i]).trim()), p);
                    symbolPrioroty.put(pri.get(place), p);
                } else {
                    i--;
                }
                place++;
            }
        }
//        for (Symbol s : symbolPrioroty.keySet()){
//            System.out.println(s+" = "+symbolPrioroty.get(s));
//        }
//        new Scanner(System.in).next();
    }

    public boolean isGeneric() {
        return generic;
    }

    public void setGeneric(boolean generic) {
        this.generic = generic;
    }

    protected void produce() {



        Vector<Symbol> sorted = lang.getSymbolsSortet();

        if (generic) {
            for (int i = 0; i < sorted.size(); i++) {
                if (Character.isDigit(sorted.get(i).getRegular())) {
                    sorted.remove(sorted.get(i));
                    i--;
                }
            }
        }

        //put symbols by priority locations:
        int loaction = 0;
        Symbol[] p1 = new Symbol[Priority1Amount];
        loaction = SortedToPriority(sorted, loaction, p1);
        Symbol[] p2 = new Symbol[Priority2Amount];
        loaction = SortedToPriority(sorted, loaction, p2);
        Symbol[] p3 = new Symbol[Priority3Amount];
        loaction = SortedToPriority(sorted, loaction, p3);
        Symbol[] p4 = new Symbol[Priority4Amount];
        loaction = SortedToPriority(sorted, loaction, p4);
        Symbol[] p5 = new Symbol[Priority5Amount];
        loaction = SortedToPriority(sorted, loaction, p5);
        Symbol[] p6 = new Symbol[Priority6Amount];
        loaction = SortedToPriority(sorted, loaction, p6);
        Permuter<Symbol> zone1 = new Permuter<Symbol>(p1);
        Permuter<Symbol> zone2 = new Permuter<Symbol>(p2);
        Permuter<Symbol> zone3 = new Permuter<Symbol>(p3);
        Permuter<Symbol> zone4 = new Permuter<Symbol>(p4);
        Permuter<Symbol> zone5 = new Permuter<Symbol>(p5);
        Permuter<Symbol> zone6 = new Permuter<Symbol>(p6);

//        total = new BigInteger("1000000000");
//        total =
        BigInteger total = zone1.getTotal().multiply(zone2.getTotal().multiply(zone3.getTotal().multiply(zone4.getTotal().multiply(zone5.getTotal().multiply(zone6.getTotal())))));
        BigInteger i = BigInteger.ONE;
        int counter = 0;
        permutatorsPersistant restorePoint = null;
        try {
            restorePoint = (permutatorsPersistant) XML_Handler.readPersistantfile("backup.xml");
            System.out.println("restoring zone 1 at: " + restorePoint.getPerm_zone1().getNumLeft());
            System.out.println("restoring zone 2 at: " + restorePoint.getPerm_zone2().getNumLeft());
            System.out.println("restoring zone 3 at: " + restorePoint.getPerm_zone3().getNumLeft());
            System.out.println("restoring zone 4 at: " + restorePoint.getPerm_zone4().getNumLeft());
            System.out.println("restoring zone 5 at: " + restorePoint.getPerm_zone5().getNumLeft());
            System.out.println("restoring zone 6 at: " + restorePoint.getPerm_zone6().getNumLeft());
            i = restorePoint.getKeyCandiID();


        } catch (Exception ex) {
            //Logger.getLogger(KeyboardProducer.class.getName()).log(Level.SEVERE, null, ex);
            restorePoint = null;
            zone1 = new Permuter<Symbol>(p1);
            zone2 = new Permuter<Symbol>(p2);
            zone3 = new Permuter<Symbol>(p3);
            zone4 = new Permuter<Symbol>(p4);
            zone5 = new Permuter<Symbol>(p5);
            zone6 = new Permuter<Symbol>(p6);
            System.out.println("no backup file found- start from scratch");
        }
        //updateAll(TOTAL + ":" + total.toString());
        try {
            for (Symbol[] z1 : zone1 = initPemutator(p1, restorePoint, 1)) {//new Permuter<Symbol>(p1)) {
                if (!run) {
                    pauseProduction(i,1, zone1, zone2, zone3, zone4, zone5, zone6);
                } else {
                    for (Symbol[] z2 : zone2 = initPemutator(p2, restorePoint, 2)) {
                        if (!run) {
                            pauseProduction(i,2, zone1, zone2, zone3, zone4, zone5, zone6);
                        } else {
                            for (Symbol[] z3 : zone3 = initPemutator(p3, restorePoint, 3)) {
                                if (!run) {
                                    pauseProduction(i,3, zone1, zone2, zone3, zone4, zone5, zone6);
                                } else {
                                    for (Symbol[] z4 : zone4 = initPemutator(p4, restorePoint, 4)) {
                                        if (!run) {
                                            pauseProduction(i,4, zone1, zone2, zone3, zone4, zone5, zone6);
                                        } else {
                                            for (Symbol[] z5 : zone5 = initPemutator(p5, restorePoint, 5)) {
                                                if (!run) {
                                                    pauseProduction(i,5, zone1, zone2, zone3, zone4, zone5, zone6);
                                                } else {
                                                    for (Symbol[] z6 : zone6 = initPemutator(p6, restorePoint, 6)) {
                                                        if (!run) {
                                                            pauseProduction(i,6, zone1, zone2, zone3, zone4, zone5, zone6);
                                                        } else {
                                                            KeyboardCandidate candi = generate(z1, z2, z3, z4, z5, z6);
                                                            candi.setID(i);
                                                            if (filter_candidats(candi)) {
                                                                success.log(Level.ALL, "keyboard produced " + NumberFormat.getNumberInstance().format(i) + ":\n" + candi.toString());
//                                    System.out.println("keyboard "+i+" passed!");
                                                                keyboards.add(candi);

                                                            } else {
                                                                success.log(Level.ALL, "keyboard " + NumberFormat.getNumberInstance().format(i) + " failed, see failed log");
                                                            }
                                                            i = i.add(BigInteger.ONE);
                                                            if (counter == updateCounter) {
                                                                updateAll(PRODUCED + ":" + i.toString() + ":" + total.toString());
                                                                counter = 0;
                                                            }
                                                            counter++;
                                                        }
                                                    }
                                                }

                                            }

                                        }

                                    }
                                }

                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            System.out.println("paused @ " + e.getMessage());
        }
    }

    private void pauseProduction(BigInteger currentID,int zone, Permuter<Symbol> pzone1, Permuter<Symbol> pzone2, Permuter<Symbol> pzone3, Permuter<Symbol> pzone4,
            Permuter<Symbol> pzone5, Permuter<Symbol> pzone6) throws Exception {

        createRestorePoint(new permutatorsPersistant(currentID,pzone1, pzone2, pzone3, pzone4, pzone5, pzone6));
        throw new Exception(String.valueOf(zone));
    }

    private int SortedToPriority(Vector<Symbol> sorted, int sortedLocation, Symbol[] p) {
        for (int i = sortedLocation, place = 0; place < p.length; i++, place++) {
            p[place] = sorted.get(i);
        }
        return sortedLocation + p.length;
    }

    private KeyboardCandidate generate(Symbol[] z1, Symbol[] z2, Symbol[] z3, Symbol[] z4, Symbol[] z5, Symbol[] z6) {
        KeyboardKey[][] keymap = new KeyboardKey[4][];
        keymap[0] = new KeyboardKey[13];
        keymap[1] = new KeyboardKey[13];
        keymap[2] = new KeyboardKey[11];
        keymap[3] = new KeyboardKey[10];

        if (generic) {
            keymap[0][1] = new KeyboardKey(lang.getNumber(1));
            keymap[0][2] = new KeyboardKey(lang.getNumber(2));
            keymap[0][3] = new KeyboardKey(lang.getNumber(3));
            keymap[0][4] = new KeyboardKey(lang.getNumber(4));
            keymap[0][5] = new KeyboardKey(lang.getNumber(5));
            keymap[0][6] = new KeyboardKey(lang.getNumber(6));
            keymap[0][7] = new KeyboardKey(lang.getNumber(7));
            keymap[0][8] = new KeyboardKey(lang.getNumber(8));
            keymap[0][9] = new KeyboardKey(lang.getNumber(9));
            keymap[0][10] = new KeyboardKey(lang.getNumber(0));
            //    updateAll("GENERIC : numeric keys are in place");
        }//if generic


        //put 1 in place
        putInPlace(keymap, z1, "1");

        //put 2 in place
        putInPlace(keymap, z2, "2");

        //put 3 in place
        putInPlace(keymap, z3, "3");

        //put 4 in place
        putInPlace(keymap, z4, "4");

        //put 5 in place
        putInPlace(keymap, z5, "5");

        //put 6 in place
        putInPlace(keymap, z6, "6");
        return new KeyboardCandidate(keymap);
    }

    private void putInPlace(KeyboardKey[][] candi, Symbol[] zone, String zoneNumber) {
        String[] locations = keyPriority.getProperty(zoneNumber).split(",");
        for (int i = 0, j = 0; i < locations.length; i++, j++) {
            int place = Integer.parseInt(locations[i].trim());
            candi[place / 100][(place % 100) - 1] = new KeyboardKey(zone[j]);
        }
    }

    private boolean isNumericPlace(int place) {
        return place >= 1 && place <= 10;
    }

    public void run() {
        //System.out.println("producing");
        this.produce();
        //System.out.println("done producing");
        updateAll("FINISH");
        unlockFiles();
    }

    private long fact(int n) {
        if (n == 0) {
            return 1;
        }

        return n * fact(n - 1);
    }

    private BigInteger calcPermutations() {
//       int ans =0;
        BigInteger firstP = BigInteger.valueOf(fact(keyPriority.getProperty("1").split(",").length));
        //System.out.println("1st : " + firstP);
        BigInteger secondP = BigInteger.valueOf(fact(keyPriority.getProperty("2").split(",").length));
        //System.out.println("2nd : " + secondP);
        BigInteger thirdP = BigInteger.valueOf(fact(keyPriority.getProperty("3").split(",").length));
        //System.out.println("3rd : " + thirdP);
        BigInteger fourthP = BigInteger.valueOf(fact(keyPriority.getProperty("4").split(",").length));
        //System.out.println("4th : " + fourthP);
        BigInteger fifthP = BigInteger.valueOf(fact(keyPriority.getProperty("5").split(",").length));
        //System.out.println("5th : " + fifthP);
        BigInteger sixthP = BigInteger.valueOf(fact(keyPriority.getProperty("6").split(",").length));
        //System.out.println("6th : " + sixthP);
        return firstP.multiply(secondP.multiply(thirdP.multiply(fourthP.multiply(fifthP.multiply(sixthP)))));
    }

    public void addObserver(Observer o) {
        observers.add(o);
    }

    public void removeObserver(Observer o) {
        observers.removeElement(o);
    }

    public void updateAll(String s) {
        for (Observer observer : observers) {
            observer.recive(this, s);
        }
    }

    private boolean filter_candidats(KeyboardCandidate candi) {
        boolean ans = false;
        Symbol[][] keymap = candi.getKeys();
        //print layout:
        int folowJ;
        // //System.out.println("filter!");
        if (keymap == null) {
            // System.err.println("STOP!!!!");
            //new Scanner(System.in).next();
            return ans;
        }
        //System.out.println("filter!\n");

        boolean p1 = false, p2 = false, p3 = false, p4 = false;
        for (int i = 0; !ans && i < keymap.length; i++) {
            for (int j = 1; !ans && j < keymap[i].length; j++) {

                Symbol tempSymbol = keymap[i][j];
                // //System.out.print(tempSymbol.getRegular());
                Symbol folowSymb = keymap[i][j - 1];

                p1 = p1 || isPair(tempSymbol, folowSymb, '(', ')');
                p2 = p2 || isPair(tempSymbol, folowSymb, '[', ']');
                p3 = p3 || isPair(tempSymbol, folowSymb, '{', '}');
                p4 = p4 || isPair(tempSymbol, folowSymb, '<', '>');
                ans = (p1 && p2 && p3 && p4);
            }
        }
        if (!ans) {
            failed.log(Level.ALL, "keyboard number " + NumberFormat.getNumberInstance().format(candi.getId()) + " failed because : ()=" + p1 + ", []=" + p2 + ", {}=" + p3 + ", <>=" + p4 + " in keyboard:\n" + candi.toString());
//            System.err.println("STOP!!!!");
//            new Scanner(System.in).next();
        }
        return ans;
    }

    private boolean isPair(Symbol tempSymbol, Symbol folowSymb, char left, char right) {
        return ((tempSymbol.getRegular().equals(left) && folowSymb.getRegular().equals(right))
                || (tempSymbol.getRegular().equals(right) && folowSymb.getRegular().equals(left))
                || (tempSymbol.getShift().equals(left) && folowSymb.getShift().equals(right))
                || (tempSymbol.getShift().equals(right) && folowSymb.getShift().equals(left)));
    }

    private boolean filter(KeyboardCandidate candi, BigInteger num) {
        boolean ans = true;
        Symbol[][] keys = candi.getKeys();
//        System.out.println("looking @");
//        System.out.println(candi);
        for (int i = 0; i < keys.length; i++) {
            for (int j = 0; j < keys[i].length; j++) {
                Integer keycodeP = priorityMap.get(i * 100 + j + 1);
                Symbol s = keys[i][j];
                Integer symbolP = symbolPrioroty.get(s);
                symbolPrioroty.keySet();
//                if (!priorityMap.get(new Integer(i*100+j+1)).equals(symbolPrioroty.get(keys[i][j]))){
                if (keycodeP != null && symbolP != null && !keycodeP.equals(symbolP)) {
//                    System.out.println("candi failed because in keycode "+(i*100+j+1)+" ["+i+","+j+"] there was "+keys[i][j]+" and it's priority is :"+symbolPrioroty.get(keys[i][j]));
//                    new Scanner(System.in).next();
                    failed.log(Level.ALL, "candidate " + NumberFormat.getNumberInstance().format(num) + ":\n" + candi.toString() + "\n failed becuse of in keycode " + (i * 100 + j + 1) + " [" + i + "," + j + "] there was " + keys[i][j] + " and it's priority is :" + symbolPrioroty.get(keys[i][j]) + " instead of " + keycodeP);
                    return false;
                }
            }
        }
//        System.out.println("candi passed!");
//        System.out.println(candi);
//        new Scanner(System.in).next();
        return ans;


    }

    public void stopAndSave() {
        System.out.println("backing up");
//        createRestorePoint(new permutatorsPersistant(zone1,zone2,zone3,zone4,zone5,zone6));
        run = false;
        unlockFiles();
    }

    private void unlockFiles(){
        for (Handler h : success.getHandlers())
            h.close();
        for (Handler h: failed.getHandlers())
            h.close();
    }

    private void createRestorePoint(permutatorsPersistant perm) {
        if (!makeRestorepoint) {
            makeRestorepoint = false;
            return;
        }
        try {
            System.out.println("backing up zone 1 at: " + perm.getPerm_zone1().getNumLeft());
            System.out.println("backing up zone 2 at: " + perm.getPerm_zone2().getNumLeft());
            System.out.println("backing up zone 3 at: " + perm.getPerm_zone3().getNumLeft());
            System.out.println("backing up zone 4 at: " + perm.getPerm_zone4().getNumLeft());
            System.out.println("backing up zone 5 at: " + perm.getPerm_zone5().getNumLeft());
            System.out.println("backing up zone 6 at: " + perm.getPerm_zone6().getNumLeft());
            XML_Handler.writeBackupPointToFile("backup.xml", perm);
            makeRestorepoint = true;
        } catch (IOException ex) {
            Logger.getLogger(KeyboardProducer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private Permuter<Symbol> initPemutator(Symbol[] p, permutatorsPersistant restorePoint, int number) {
        Permuter<Symbol> ans = null;
        if (restorePoint != null) {

            switch (number) {
                case 1:
                    ans = restorePoint.getPerm_zone1();
                    break;
                case 2:
                    ans = restorePoint.getPerm_zone2();
                    break;
                case 3:
                    ans = restorePoint.getPerm_zone3();
                    break;
                case 4:
                    ans = restorePoint.getPerm_zone4();
                    break;
                case 5:
                    ans = restorePoint.getPerm_zone5();
                    break;
                case 6:
                    ans = restorePoint.getPerm_zone6();
                    break;
            }
            return ans;
        }
//       switch (number) {
        ans = new Permuter<Symbol>(p);
        /* case 2: ans=new Permuter<Symbol>(p);break;
        case 3: ans=new Permuter<Symbol>(p);break;
        case 4: ans=new Permuter<Symbol>(p);break;
        case 5: ans=new Permuter<Symbol>(p);break;
        case 6: ans=new Permuter<Symbol>(p);break;
        //            }*/
        return ans;
    }
}






